package com.szwistar.common.datastruct;

import java.util.Arrays;

public class BitArray {
	/**
	 * Values are stored in a byte array
	 */
	protected final byte[] bitmap;

	/**
	 * Number of elements in bitmap
	 */
	protected final int size;

	public byte[] getBitmap() { return bitmap; }
	public int getSize() { return size; }

	public BitArray() {
		this(8, false);
	}

	public BitArray(int size) {
		this(size, false);
	}

	public BitArray(byte[] bitmap) {
		this.bitmap = Arrays.copyOf(bitmap, bitmap.length);
		size = bitmap.length * 8; // length << BYTE_SHIFT
	}

	/**
	 * Create a bitmap
	 * @param size number of elements in bitmap
	 * @param set true : set all available bits to 1
	 *            false : all available bits remain 0
	 */
	public BitArray(int size, boolean set) {
		int bytes = (int) Math.ceil((double) size / 8); //number of bytes

		bitmap = new byte[bytes];
		this.size = size;
		if(set) {
			for (int i = 0; i < size; i++) set(i);
		}
	}

	/**
	 * Set the position on bitmap to 1
	 * @param position position on bitmap
	 */
	public void set(int position) {
		if (position > size) return;

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);

		bitmap[byteOffset] |= (byte) (1 << bitOffset);
	}

	public void clear(int position) {
		if (position > size) return;

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		byte complement = (byte) ~(1 << bitOffset);

		bitmap[byteOffset] &= complement;
	}

	/**
	 * Get the value of the position on bitmap
	 * @param position position on bitmap
	 * @return true if the position is 1,false else
	 */
	public boolean get(int position) {
		if (position > size) return false;

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);

		return (byte) (bitmap[byteOffset] & (1 << bitOffset)) != 0;
		//this value may return -128 if the highest bit is 1(signed)
	}

	protected int getByteOffset(int position) {
		return bitmap.length - 1 - (position >> 3);
	}

	public BitArray not() {
		for (int i = 0; i < bitmap.length; i++)
			bitmap[i] = (byte) ~bitmap[i];
		return this;
	}

	public BitArray and(BitArray map) {
		if (map.getSize() != size) return null;
		BitArray result = new BitArray(map.getBitmap());
        byte[] byteArray = result.getBitmap();

		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] &= bitmap[i];

		return result;
	}

	public BitArray or(BitArray map) {
		if (map.getSize() != size) return null;
		BitArray result = new BitArray(map.getBitmap());
		byte[] byteArray = result.getBitmap();

		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] |= bitmap[i];

		return result;
	}

	public BitArray xor(BitArray map) {
		if (map.getSize() != size) return null;
		BitArray result = new BitArray(map.getBitmap());
        byte[] byteArray = result.getBitmap();

		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] ^= bitmap[i];

		return result;
	}

	public BitArray nor(BitArray map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		BitArray result = new BitArray(size);

		for (int i = 0; i < size; i++)
			if (!get(i) && !map.get(i))
				result.set(i);

		return result;
	}

	public BitArray xnor(BitArray map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		BitArray result = new BitArray(size);

		for (int i = 0; i < size; i++)
			if (get(i) == map.get(i))
				result.set(i);

		return result;
	}

	public BitArray nand(BitArray map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		BitArray result = new BitArray(size);

		for (int i = 0; i < size; i++) {
			if (!(get(i) && map.get(i)))
				result.set(i);
		}

		return result;
	}

	/**
	 * NOT
	 * complement_a[i] := not a[i]
	 * @param a
	 * @return
	 */
	public static BitArray complement(BitArray a) {
		int size = a.getSize();

		for (int i = 0; i < size; i++)
			if (a.get(i))
				a.clear(i);
			else
				a.set(i);

		return a;
	}

	/**
	 * OR
	 * union[i] := a[i] or b[i]
	 * @param a
	 * @param b
	 * @return
	 */
	public static BitArray union(BitArray a, BitArray b) {
		if (a.getSize() != b.getSize())
			return null;

		BitArray map = new BitArray(a.getSize());
		int size = a.getSize();

		for (int i = 0; i < size; i++)
			if (a.get(i) || b.get(i))
				map.set(i);

		return map;
	}

	/**
	 * AND
	 * intersection[i] := a[i] and b[i]
	 * @param a
	 * @param b
	 * @return
	 */
	public static BitArray intersection(BitArray a, BitArray b) {
		if (a.getSize() != b.getSize())
			return null;

		BitArray map = new BitArray(a.getSize());
		int size = a.getSize();

		for (int i = 0; i < size; i++)
			if (a.get(i) && b.get(i))
				map.set(i);

		return map;
	}

	/**
	 * XOR
	 * difference[i] := a[i] and (not b[i])
	 * @param a
	 * @param b
	 * @return
	 */
	public static BitArray difference(BitArray a, BitArray b) {
		if (a.getSize() != b.getSize())
			return null;

		BitArray map = new BitArray(a.getSize());
		int size = a.getSize();

		for (int i = 0; i < size; i++)
			if (a.get(i) != b.get(i))
				map.set(i);

		return map;
	}

	/**
	 * Returns the binary representation of bitmap.
	 * @return binary represetation of bitmap as String
	 */
	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();

		for (int i = 0; i < size; i++)
			if (get(i)) result.append("1");
			else result.append("0");

		return result.toString();
	}
}
